﻿namespace Frameworks
{
    using System;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using UnityEngine;
    using UnityEngine.Events;

    public static class Debugger
    {
        public const int MaxlogsToKeep = 999;
        public const string UnityInternalNewLine = "\n";

        private static bool s_isCallBackSet;
        private static bool s_isOnConsole;
        private static bool s_isOnScreen;
        private static bool s_isOnText;

        private static long s_startTick;
        private static bool s_alreadyLogging = false;
        private static LinkedList<LogInfo> s_logCaches = new LinkedList<LogInfo>();

        private static LoggerAppWindow s_loggerWindow;
        private static LoggerFile s_loggerFile;
        
        public static string LogPath { get; set; }
        public static UnityAction<string> GM_Command { get; set; }

        static Debugger()
        {
            s_startTick = DateTime.Now.Ticks;
        }

        #region public
        public static void EnableDebugLogs(bool isOn)
        {
            s_isOnConsole = isOn;
            if (s_isOnConsole)
            {
                if (!s_isCallBackSet)
                {
                    s_isCallBackSet = true;
                    Application.logMessageReceivedThreaded += UnityLogHandler;
                    //Log("Debugger start up success");
                }
            }
            else
            {
                if (s_isCallBackSet)
                {
                    s_isCallBackSet = false;
                    Application.logMessageReceivedThreaded -= UnityLogHandler;

                    s_isOnConsole = false;
                    s_isOnScreen = false;
                    s_isOnText = false;
                }
            }
        }

        public static void EnableOnScreen(bool isOn)
        {
            s_isOnScreen = isOn;
            if (s_isOnScreen)
            {
                if (s_loggerWindow == null)
                {
                    GameObject target = new GameObject("LoggerWindow");
                    s_loggerWindow = target.AddComponent<LoggerAppWindow>();
                }

                if (!s_isOnConsole)
                    EnableDebugLogs(true);
            }
        }

        public static void EnableOnText(bool isOn)
        {
            s_isOnText = isOn;
            if (s_isOnText)
            {
                if (s_loggerFile == null)
                {
                    if (string.IsNullOrEmpty(LogPath))
                        LogPath = Application.dataPath + "/../AppLog.txt";

                    s_loggerFile = new LoggerFile(LogPath);
                }

                if (!s_isOnConsole)
                    EnableDebugLogs(true);
            }
        }

        public static void Log(object message)
        {
            if (s_isOnConsole)
            {
                Debug.Log("# " + message);
            }
        }

        public static void LogError(object message)
        {
            if (s_isOnConsole)
            {
                Debug.LogError("# " + message);
            }
        }

        public static void LogWarning(object message)
        {
            if (s_isOnConsole)
            {
                Debug.LogWarning("# " + message);
            }
        }

        public static void Destroy()
        {
            if (s_loggerFile != null)
            {
                s_loggerFile.Dispose();
            }
        }
        
        public static double GetRelativeTime()
        {
            long ticks = DateTime.Now.Ticks;
            return TimeSpan.FromTicks(ticks - s_startTick).TotalSeconds;
        }
        #endregion

        #region private
        private static void UnityLogHandler(string condition, string stackTrace, LogType type)
        {
            lock (s_logCaches)
            {
                if (!s_alreadyLogging)
                {
                    try
                    {
                        s_alreadyLogging = true;

                        var callstack = GetCallstackFromUnityLog(stackTrace);
                        var logInfo = new LogInfo(null, "", type, callstack, condition);

                        s_logCaches.AddLast(logInfo);
                        TrimOverLimitLogs();

                        OnText(logInfo);
                        OnScreen(logInfo);
                    }
                    finally
                    {
                        s_alreadyLogging = false;
                    }
                }
            }
        }

        private static List<LogStackFrame> GetCallstackFromUnityLog(string unityCallstack)
        {
            var lines = Regex.Split(unityCallstack, UnityInternalNewLine);

            var stack = new List<LogStackFrame>();
            foreach (var line in lines)
            {
                var frame = new LogStackFrame(line);
                if (!string.IsNullOrEmpty(frame.FormattedMethodNameWithFileName))
                {
                    stack.Add(frame);
                }
            }
            return stack;
        }

        private static void TrimOverLimitLogs()
        {
            while (s_logCaches.Count > MaxlogsToKeep)
            {
                s_logCaches.RemoveFirst();
            }
        }

        private static void OnScreen(LogInfo logInfo)
        {
            if (s_isOnScreen)
            {
                if (s_loggerWindow != null)
                {
                    s_loggerWindow.Log(logInfo);
                }
            }
        }

        private static void OnText(LogInfo logInfo)
        {
            if (s_isOnText && s_loggerFile != null)
            {
                s_loggerFile.Log(logInfo);
            }
        }
        #endregion
    }
}